/* sprite-sheet-window.vala
 *
 * Copyright (C) 2010, 2011  Richard Talbot-Watkins
 * Copyright (C) 2010, 2011  Dave Jeffery
 * 
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Authors:
 *  Richard Talbot-Watkins <richtw1@gmail.com>
 * 	Dave Jeffery <david.richard.jeffery@gmail.com>
 */

using Gtk;

/**
 * The Sprite Sheet Window for Beeb Spriter.
 *
 * @author Richard Talbot-Watkins <richtw1@gmail.com>
 * @author Dave Jeffery <david.richard.jeffery@gmail.com>
 * @version 2010.1231
 * @since 0.1
 */
public class BeebSpriter.SpriteSheetWindow : Window {
	/**
	 * Sprite sheet instance displayed by window.
	 */
	private SpriteSheet sprite_sheet;

	/**
	 * Menu bar for the Sprite Sheet window. 
	 */
	private SpriteSheetMenuBar menu_bar;

	/**
	 * Scrolled Window to hold the Sprite Sheet widget.
	 */
	private ScrolledWindow scrolled_window;

	/**
	 * Sprite sheet widget, used to interact with sprite sheets.
	 */
	private SpriteSheetWidget sprite_sheet_widget;

	/**
	 * Keyboard accelerators used in the Sprite Sheet window. */
	private AccelGroup accelerator_group;

	/**
	 * File object containing the user's data directory e.g. ~/Documents.
	 */
	private File file = FileUtilities.get_user_data_directory ();

	/**
	 * File filters to add to file chooser if saving .bspr files.
	 */
	private FileFilter[] bspr_filters = 
						{FileUtilities.bspr_filter, FileUtilities.all_filter};

	/**
	 * Set when there is anything pending which should be saved.
	 */
	private bool _is_unsaved = false;        

	/**
	 * Getter/Setter for the is_unsaved property. 
	 *
	 * The setter updates the title bar.
	 */
	private bool is_unsaved {
		get { 
			return _is_unsaved; 
		}
		set {   
			bool old_state = _is_unsaved;
            _is_unsaved = value;

			if (old_state != _is_unsaved) {
				set_window_title ();
			}
		}
	}

	/**
	 * Set to true when the no file is currently open in the editor.
	 */
	private bool is_closed = true;

	/**
	 * Constructor for the sprite sheet form window.
	 */
	public SpriteSheetWindow () {
		/* Connect handler to close icon */
		delete_event.connect (on_delete_event);

		/* Window title */
		set_window_title ();

		/* Window size */
		set_default_size (384, 428);

		/* Window icon */
		this.icon = FileUtilities.load_icon ("beeb-spriter", 16);

		/* Window accelerator group */
		accelerator_group = new AccelGroup ();
		add_accel_group (accelerator_group);

		/* Create window menu bar */
		menu_bar = new SpriteSheetMenuBar (accelerator_group);

		/* Connect signals to menu bar items */
		connect_menu_signals ();

		/* Create a scrolled window to hold sprite sheet widget. */
		scrolled_window = new ScrolledWindow (null, null);
		scrolled_window.set_policy (PolicyType.AUTOMATIC, 
													PolicyType.AUTOMATIC);

		/* Pack the menu bar and flow panel into a VBox */
		var vbox = new VBox (false, 4);
		vbox.pack_start (menu_bar, false, false, 0);
		vbox.pack_start (scrolled_window, true, true, 0);

		/* Add the VBox to the window. */
		add (vbox);

		/* Register the window as a drag and drop destination. */
		drag_dest_set (this, destination_flags, targets, actions);

		/* Set up a handler for 'drop' events. */
		drag_data_received.connect (on_drag_data_received);
	}

	/**
	 * Event called when New clicked from the File menu.
	 */
    private void on_file_new_activated () {
		create_new ();
	}

	/**
	 * Called when Open... is selected from the File menu.
	 */
	private void on_file_open_activated () {
		/* Check if the user wishes to save or continue unsaved work. */
		if (continue_current_work (UserAction.OPEN)) { return; };
		
		/* Call generic open function. */
		open ();
	}

	/**
	 * Called when Save is selected from the File menu.
	 */
    private void on_file_save_activated () {
		/* If the file doesn't exist, show a save dialog box. */
		if (!file.query_exists () ||
				file.query_file_type (0) == FileType.DIRECTORY) {
			if (save_filechooser (ref file, _("Save"), bspr_filters)  
													!= ResponseType.ACCEPT) {
				return;
			}
		} else {
			/* If it does, but there's nothing to save, do nothing. */
			if (!is_unsaved) { return; }
		}

		/* Call generic save function. */
		save();
	}

	/**
	 * Called when Save As... is selected from the File menu.
	 */
	private void on_file_save_as_activated () {
		/* Call generic save function. */
		if (save_filechooser (ref file, _("Save As..."), bspr_filters) 
													== ResponseType.ACCEPT) {
			save();
		}
	}

	/**
	 * Called when Export Settings... is selected from the File menu.
	 */
	private void on_export_settings_activated () {
		var dialog = new ExportSettingsDialog (this, sprite_sheet);
		dialog.run ();
		dialog.destroy ();
	}

	/**
	 * Called when Export to Beeb... is selected from the File menu.
	 */
	private void on_export_to_beeb_activated () {
		/* Create a file filter for .bin and .bin.info files */
		FileFilter[] bin_filters = {FileUtilities.bin_filter, 
					FileUtilities.bin_info_filter, FileUtilities.all_filter};

		/* Create file object for the .bin file */
		var bin = file.get_basename ().replace (".bspr", ".bin");
		var bin_file = file.get_parent ().get_child (bin);

		var title = _("Export to Beeb...");
		if (save_filechooser (ref bin_file, title, bin_filters) 
													== ResponseType.ACCEPT) {
			export (bin_file);
		}
	}

	/**
	 * Event called when Close is clicked from the File menu.
	 */
	private void on_file_close_activated () {
		/* Check if the user wishes to save or continue unsaved work. */
		if (continue_current_work (UserAction.CLOSE)) { return; };

		/* Call generic close function. */
		set_closed ();
	}

	/**
	 * Event called when Quit selected from File Menu.
	 *
	 * Emits a Gtk.Widget delete_event signal if the quit option is
	 * selected from the file menu. This means it works in exactly the
	 * same way as the close button on the window menu bar, and can be
	 * handled using the window menu bar close button's event handler.
	 */
	private void on_file_quit_activated () {
		delete_event (new Gdk.Event(Gdk.EventType.DELETE));
	}

	/**
	 * Event called when Default selected on Background Menu.
	 */
	private void on_background_transparent_activated () {
		if (menu_bar.background_transparent.active) {
			sprite_sheet.background_colour = SpriteSheet.BackColour.THEME_DEFAULT;
		}
	}

	/**
	 * Event called when Black selected on Background Menu.
	 */
	private void on_background_black_activated () {
		if (menu_bar.background_black.active) {
			sprite_sheet.background_colour = SpriteSheet.BackColour.BLACK;
		}
	}

	/**
	 * Event called when Red selected on Background Menu.
	 */
	private void on_background_red_activated () {
		if (menu_bar.background_red.active) {
			sprite_sheet.background_colour = SpriteSheet.BackColour.RED;
		}
	}

	/**
	 * Event called when Green selected on Background Menu.
	 */
	private void on_background_green_activated () {
		if (menu_bar.background_green.active) {
			sprite_sheet.background_colour = SpriteSheet.BackColour.GREEN;
		}
	}

	/**
	 * Event called when Yellow selected on Background Menu.
	 */
	private void on_background_yellow_activated () {
		if (menu_bar.background_yellow.active) {
			sprite_sheet.background_colour = SpriteSheet.BackColour.YELLOW;
		}
	}

	/**
	 * Event called when Blue selected on Background Menu.
	 */
	private void on_background_blue_activated () {
		if (menu_bar.background_blue.active) {
			sprite_sheet.background_colour = SpriteSheet.BackColour.BLUE;
		}
	}

	/**
	 * Event called when Magenta selected on Background Menu.
	 */
	private void on_background_magenta_activated () {
		if (menu_bar.background_magenta.active) {
			sprite_sheet.background_colour = SpriteSheet.BackColour.MAGENTA;
		}
	}

	/**
	 * Event called when Cyan selected on Background Menu.
	 */
	private void on_background_cyan_activated () {
		if (menu_bar.background_cyan.active) {
			sprite_sheet.background_colour = SpriteSheet.BackColour.CYAN;
		}
	}

	/**
	 * Event called when White selected on Background Menu.
	 */
	private void on_background_white_activated () {
		if (menu_bar.background_white.active) {
			sprite_sheet.background_colour = SpriteSheet.BackColour.WHITE;
		}
	}

	/**
	 * Called when About... is selected from the Help menu.
	 */
	private void on_help_about_activated () {
		var about_dialog = new Credits (this);
		about_dialog.run ();
		about_dialog.destroy ();
	}

	/**
	 * Called when file is dragged into the main window.
	 *
	 * @param context
	 * @param selection_data
	 * @param target_type
	 * @param time
	 */
	private void on_drag_data_received (Gdk.DragContext context,
	                                    int x, int y,
	                                    SelectionData selection_data,
	                                    uint target_type, uint time) {
		var uris = selection_data.get_uris ();

		/* We can only cope with one file, so only handle the first file of
		   group of files dragged into the Monster Designer window. */
		var drag_file = File.new_for_uri (uris[0]);

		/* Not interested in non-existent files or in directories. */
		if (!drag_file.query_exists () || 
				drag_file.query_file_type (0) == FileType.DIRECTORY) {
			return;
		}

		/* Check if the user wishes to save or continue unsaved work. */
		if (continue_current_work (UserAction.OPEN)) { return; }

		/* Attempt to load the file dragged into the window. */
		file = drag_file;
		load_file ();
	}

	/**
	 * Event called when close icon clicked on the title bar.
	 *
	 * Called by the Gtk.Widget delete_event signal. Note that, in common
	 * with Gedit, no Save dialog box is displayed if you click Save - the
	 * file is just saved and the program quits.
	 *
	 * @return If we want the program to quit then false, otherwise true.
	 */
	private bool on_delete_event () {
		/* Check if the user wishes to save or continue unsaved work. */
		if (continue_current_work (UserAction.CLOSE)) { return true; }

		Gtk.main_quit ();
		return false;
	}

	/**
	 * Initialises the background colour menu radio button when a new file is
	 * loaded.
	 */
	private void set_background_menu_radio () {
		switch (sprite_sheet.background_colour) {
			case SpriteSheet.BackColour.BLACK:
				menu_bar.background_black.active = true;
				break;
			case SpriteSheet.BackColour.RED:
				menu_bar.background_red.active = true;
				break;
			case SpriteSheet.BackColour.GREEN:
				menu_bar.background_green.active = true;
				break;
			case SpriteSheet.BackColour.YELLOW:
				menu_bar.background_yellow.active = true;
				break;
			case SpriteSheet.BackColour.BLUE:
				menu_bar.background_blue.active = true;
				break;
			case SpriteSheet.BackColour.MAGENTA:
				menu_bar.background_magenta.active = true;
				break;
			case SpriteSheet.BackColour.CYAN:
				menu_bar.background_cyan.active = true;
				break;
			case SpriteSheet.BackColour.WHITE:
				menu_bar.background_white.active = true;
				break;
			default:
				menu_bar.background_transparent.active = true;
				break;
		}
	}

	/**
	 * Called whenever the sprite sheet background colour changes.
	 */
	private void on_background_changed () {
		is_unsaved = true;
	}

	/**
	 * Called whenever one of the sprite sheet's sprites changes.
	 */
	private void on_sprite_changed () {
		is_unsaved = true;
	}

	/**
	 * Called whenever the sprite sheet's sprite order changes.
	 */
	private void on_order_changed () {
		is_unsaved = true;
	}

	/**
	 * Called whenever the sprite sheet's export settings changes.
	 */
	private void on_settings_changed () {
		is_unsaved = true;
		sprite_sheet.has_export_settings = true;
	}

	/**
	 * Sets the window title, according to the currently loaded file and 
	 * whether there is unsaved work.
	 */
	private void set_window_title () {
		var title_text = "";

		if (!is_closed) {
			/* Add asterisk. */
			if (is_unsaved) {
				title_text += "*";
			}
			
			/* Add file name. */
			title_text += FileUtilities.get_file_name (file);

			/* Add add file path. */
			if (file.query_exists ()) {
				var path = file.get_parent ();
				var path_name = path.get_parse_name ();
				title_text += " (" + path_name + ") ";		
			}

			/* Add BBC Micro/Acorn Electron screen mode. */
			if (sprite_sheet != null) {
				title_text += " Mode " + sprite_sheet.mode.to_string ();
			}

			/* Add dash */
			title_text += " - ";
		}

		/* Add program name. */
		title_text += "Beeb Spriter";
		title = title_text;
	}

	/**
	 * When a new sprite is created, or an existing one is loaded,
	 * call this method to set up the form ready for editing.
	 */
    private void set_open()
    {
		/* Create sprite sheet widget and add to scrolled window. */
		sprite_sheet_widget = new SpriteSheetWidget (sprite_sheet, this);

		/* Show the sprite sheet widget. */	
		if (scrolled_window.child == null) {
			scrolled_window.add (sprite_sheet_widget);
		}

		scrolled_window.show_all ();

		/* Enable available menu options */
		menu_bar.file_save.set_sensitive (true);
		menu_bar.file_save_as.set_sensitive (true);
		menu_bar.file_export_settings.set_sensitive (true);
		menu_bar.file_export_to_beeb.set_sensitive (true);
		menu_bar.file_close.set_sensitive (true);
		menu_bar.file_revert.set_sensitive (true);
		menu_bar.file_export_to_beeb.set_sensitive (true);
		menu_bar.tools_menu_button.show ();
		menu_bar.background_menu_button.show ();

		/* Update background colour menu option. */
		set_background_menu_radio ();

		/* Connect sprite sheet signals */
		sprite_sheet.background_changed.connect (on_background_changed);
		sprite_sheet.sprite_changed.connect (on_sprite_changed);
		sprite_sheet.order_changed.connect (on_order_changed);
		sprite_sheet.settings_changed.connect (on_settings_changed);

		/* Set as open.*/
		is_closed = false;

		/* Update title bar - must be done after is_closed has been set.*/
		set_window_title ();
    }

	/**
	 * Set the window to a closed state, i.e. without any .bspr file open.
	 */
	public void set_closed () {
		is_unsaved = false;
		is_closed = true;
		set_window_title ();

		/* Hide the sprite sheet widget. */
        hide_sprite_sheet_widget ();

		/* Grey out unavailable menu options. */
		menu_bar.file_save.set_sensitive (false);
		menu_bar.file_save_as.set_sensitive (false);
		menu_bar.file_export_settings.set_sensitive (false);
		menu_bar.file_export_to_beeb.set_sensitive (false);
		menu_bar.file_close.set_sensitive (false);
		menu_bar.file_revert.set_sensitive (false);
		menu_bar.file_export_to_beeb.set_sensitive (false);
		menu_bar.tools_menu_button.hide ();
		menu_bar.background_menu_button.hide ();
    }

	/**
	 * Common routine called when a new file is to be created.
	 *
	 * @param mode BBC Screen mode (or null to get it from a dialog box).
	 */
	private void create_new () {
        /* Check if the user wishes to save or continue unsaved work. */
		if (continue_current_work (UserAction.NEW)) { return; };

		/* Display the mode selection dialog */
		var dialog_mode = create_dialog ();
		if (dialog_mode == null) {
			return;
		}

		/* Hide and destroy the current file's sprite sheet widget. */
        hide_sprite_sheet_widget ();

		/* dialog_mode is a nullable int, so a cast is needed. */
		var mode = (int) dialog_mode;

		/* Give the sprite sheet a new, non-extant, temporary file name. */
		file = FileUtilities.next_free_filename ();

		/* Create an empty sprite sheet for screen mode. */
		sprite_sheet = new SpriteSheet.with_mode (mode);

		/* Set as unsaved. */
		is_unsaved = false;

		/* Enable the sprite sheet window. */
        set_open();
	}

	/**
	 * Common routine called when a file is to be opened.
	 */
	private void open () {
		/* Display Open dialog box */					
		var open_box = new OpenFileDialog (file, _("Open"), bspr_filters);
		open_box.set_transient_for (this);
		var open_outcome = open_box.run ();
		if (open_outcome == ResponseType.ACCEPT) {
			file = open_box.get_file ();
		}
		open_box.destroy ();

		/* Load file */
		if (open_outcome == ResponseType.ACCEPT) {		
			load_file ();
		}
	}

	/**
	 * Loads the file stored in the file object.
	 */
	private void load_file () {
		try {
			/* Try to load and parse file. */
			sprite_sheet = Bspr.read (file);

			/* Hide and destroy the current file's sprite sheet widget. */
		    hide_sprite_sheet_widget ();

			/* Set as saved. */
			is_unsaved = false;

			/* Create a sprite sheet widget for the new file. */
			set_open ();		
		} catch (BsprError e) {
			var file_name = FileUtilities.get_file_name (file);
			/* This rather odd syntax is necessary for i18n. */
			var message = 
			_("<big><b>There was a problem with the file \"%s\".</b></big>")
			.printf (file_name);
			file_error_dialog (message, e.message);
			return;
		}
	}

	/**
	 * Saves spritesheet instance to filename stored in file object.
	 */
	private void save () {
		try {
			/* Try to save the sprite sheet to a file. */
			Bspr.write (sprite_sheet, file);
			is_unsaved = false;
			set_window_title ();
		} catch (BsprError e) {
			var file_name = FileUtilities.get_file_name (file);
			/* This rather odd syntax is necessary for i18n. */
			var message = 
			_("<big><b>There was a problem saving the file \"%s\".</b></big>")
			.printf (file_name);
			file_error_dialog (message, e.message);
		}
	}

	/**
	 * Exports the sprite sheet to the BBC binary file stored in file object.
	 *
	 * Will store a BBC assembler header file as well if that is specified in
	 * the sprite sheet.
	 *
	 * @param bin_file File object containing binary file to save.
	 */
	private void export (File bin_file) {
		/* Create file object for the .bin.info file */
		var bin_info = bin_file.get_basename ().replace (".bin", ".bin.info");
		var bin_info_file = bin_file.get_parent ().get_child (bin_info);

		/* Export sprite sheet in binary form. */
		try {
			var bbc_binary = new BBCBinary ();
			bbc_binary.write (sprite_sheet, bin_file, bin_info_file);
		} catch (BBCBinaryError e) {
			var file_name = FileUtilities.get_file_name (bin_file);
			/* This rather odd syntax is necessary for i18n. */
			var message = 
			_("<big><b>There was a problem exporting the file \"%s\".</b></big>")
			.printf (file_name);
			file_error_dialog (message, e.message);
		}
	}

	/**
	 * Hides the sprite sheet widget, and disposes of it safely.
	 */
	private void hide_sprite_sheet_widget () {
        scrolled_window.hide ();
		if (scrolled_window.child != null) {
			scrolled_window.remove (sprite_sheet_widget);
			sprite_sheet_widget.destroy ();
		}
	}

	/**
	 * Checks if the user wishes to save or continue unsaved work.
	 *
	 * Displays a dialog that gives the user the opportunity to save their
	 * work or cancel the current file creation, open or close operation.
	 *
	 * @return true continue editing the current document, otherwise false.
	 */
	private bool continue_current_work (UserAction user_action) {
		/* No unsaved work, so do nothing. */
		if (!is_unsaved) { return false; }

		/* Determine dialog box text. */
		var file_name = FileUtilities.get_file_name (file);
		var message = "";
		var button_text = "";

		/* The rather odd use of printf is necessary for i18n. */
		switch (user_action) {
			case UserAction.NEW:
				message = _("<big><b>Save changes to document \"%s\" before creating a new file?</b></big>").printf (file_name);
				button_text = _("Create _without Saving");
				break;
			case UserAction.OPEN:
				message = _("<big><b>Save changes to document \"%s\" before opening a new file?</b></big>").printf (file_name);
				button_text = _("Open _without Saving");
				break;
			case UserAction.CLOSE:
				message = _("<big><b>Save changes to \"%s\" before closing?</b></big>").printf (file_name);
				button_text = _("Close _without Saving");
				break;
		}

		/* Run the save check dialog box. */
		var response = save_check_dialog (message, button_text);

		switch (response) {
			case ResponseType.YES:
				/* Save. */
				save ();
				return false;
			case ResponseType.NO:
				/* Do nothing. */
				return false;
			default:
				/* User wants to keep editing the current document. */
				return true;
		}
	}

	/**
	 * Connects the menu bar signal handlers to menu bar signals.
	 */
	private void connect_menu_signals () {
		menu_bar.file_new.activate.connect (on_file_new_activated);
		menu_bar.file_open.activate.connect (on_file_open_activated);
		menu_bar.file_save.activate.connect (on_file_save_activated);
		menu_bar.file_save_as.activate.connect (on_file_save_as_activated);
		menu_bar.file_export_settings.activate.connect
										(on_export_settings_activated);
		menu_bar.file_export_to_beeb.activate.connect
										(on_export_to_beeb_activated);
		menu_bar.file_close.activate.connect (on_file_close_activated);
		menu_bar.file_quit.activate.connect (on_file_quit_activated);

		menu_bar.background_transparent.activate.connect 
										(on_background_transparent_activated);
		menu_bar.background_black.activate.connect 
										(on_background_black_activated);
		menu_bar.background_red.activate.connect 
										(on_background_red_activated);
		menu_bar.background_green.activate.connect 
										(on_background_green_activated);
		menu_bar.background_yellow.activate.connect 
										(on_background_yellow_activated);
		menu_bar.background_blue.activate.connect 
										(on_background_blue_activated);
		menu_bar.background_magenta.activate.connect 
										(on_background_magenta_activated);
		menu_bar.background_cyan.activate.connect 
										(on_background_cyan_activated);
		menu_bar.background_white.activate.connect 
										(on_background_white_activated);

		menu_bar.help_about.activate.connect (on_help_about_activated);
	}

	/**
	 * Displays a FileChooser widget for saving .bspr or .bin files.
	 *
	 * If user decides to save, the user selected path is put in file_to_save.
	 *
	 * @param file_to_save A suggested path for the file to save.
	 * @param title The title for the file chooser widget.
	 * @param filters The file filters to be applied to the file chooser.
	 * @return FileChooser widget response.
	 */
	private int save_filechooser (ref File file_to_save, string title, 
													FileFilter[] filters) {
		/* Display a Save FileChooser widget. */
		var save_box = new SaveFileDialog (file_to_save, title, filters);
		save_box.set_transient_for (this);
		save_box.do_overwrite_confirmation = true;
		var save_outcome = save_box.run ();

		/* Store file path if user decides to save a file. */
		if (save_outcome == ResponseType.ACCEPT) {
			file_to_save = save_box.get_file ();
		}
		save_box.destroy ();

		return save_outcome;
	}

	/**
	 * Called to see if the user wants to save a file before it is closed.
	 *
	 * Displays a warning alert offering the user the chance to save
	 * their work before the file is closed.
	 * 
	 * @param message dialog box message (pango formatted)
	 * @param button_text text that will appear on the dialog No button
	 * @return dialog response
	 */
	private int save_check_dialog (string message,	string button_text) {
		var result = new MessageDialog.with_markup (this, 
									DialogFlags.MODAL | 
									DialogFlags.DESTROY_WITH_PARENT,
									MessageType.WARNING, 
									ButtonsType.NONE,
									message);
		result.secondary_text = 
					_("Your changes will be lost if you don't save them.");
		result.add_button (button_text, ResponseType.NO);
		result.add_button (STOCK_CANCEL, ResponseType.CANCEL);
		result.add_button (STOCK_SAVE, ResponseType.YES);
		result.set_default_response (ResponseType.YES);

		var response = result.run ();
		result.destroy ();

		return response;
	}

	/**
	 * Standard Error Alert Dialog box displayed if there is a file error.
	 * 
	 * @param message dialog message (pango formatted)
	 * @param secondary_text secondary text (pango formatted)
	 */
	public void file_error_dialog (string message, string secondary_text) {
		var alert = new MessageDialog.with_markup (this, 
									DialogFlags.MODAL | 
									DialogFlags.DESTROY_WITH_PARENT,
									MessageType.ERROR, 
									ButtonsType.OK,
									message);
		alert.secondary_text = secondary_text;
		alert.run ();
		alert.destroy ();
	}

	/**
	 * Dialog box used to select a mode when creating new sprite sheet.
	 *
	 * @return A BBC/Electron mode or null if the dialog was cancelled.
	 */
	private int? create_dialog () {
		var dialog = new NewSpriteSheetDialog (this);
		var response = dialog.run();
		var mode = dialog.get_selected_mode ();
		dialog.destroy();

		if (response == ResponseType.APPLY) {
			return mode;
		} else {
			return null;
		}
	}
}
